<style type="text/css">
  .myapp .float {
    margin: 0 10px 10px 0;
  }
  .title {
    text-align: center;
    background-color: black;
    color: #fff;
  }
  .k {
    margin-bottom: 10px;
  }
</style>
<div class="bfc myapp">
  <div class="w200 float">
    <h3 class="title">SVG</h3>

    <div class="w200 h200 k">
      <svg width="200" height="200" class='box mesh'>
        <defs>
          <marker id="arrow" markerWidth="7" markerHeight="10" refX="0" refY="5" orient="auto">
            <path d="M0,0 L7,5 L0,10 Z"></path>
          </marker>
        </defs>
        <path d='M10 10 L90 10 A 5 5 0 0 1 100 20' fill='none' stroke='black' marker-end='url(#arrow)'></path>
      </svg>
    </div>

    <div class="w200 h200 k">
      <svg width="200" height="200" class='box mesh'>
        <path d='M0,0 L7,5 L0,10 Z'></path>
      </svg>
    </div>

  </div>
    
  <div class="w200 float">
    <h3 class="title">Canvas</h3>

    <div class="w200 h200 k">
      <canvas width="200" height="200" class='box mesh' id='canvas1'></canvas>
      <script>
        (function() {
          var elem = document.getElementById('canvas1');
          var ctx = elem.getContext('2d')
          ctx.moveTo(10, 10)
          ctx.arcTo(100, 10, 100, 100, 10)
          ctx.lineTo(100, 100);
          ctx.stroke()
        }())
      </script>
    </div>

  </div>

  <div class="w500 float">
    <h3 class="title">Application</h3>

    <div class="w500 500 k">
      <svg width="500" height="500" class='box mesh' id='svg-3-1'>
        <defs>
          <marker id='arrow2' markerWidth='7' markerHeight='10' refX='17' refY='5' orient='auto'>
            <path d='M0,0 L7,5 L0,10 Z' />
          </marker>
          <marker id='arrow3' markerWidth='7' markerHeight='10' refX='17' refY='5' orient='auto'>
            <path d='M0,0 L7,5 L0,10 Z' fill='red' />
          </marker>
        </defs>
      </svg>
      <script>
        const TOP_LEVEL = 4;
        /**
         * 获取一个node的第level级子节点
         * @param node
         * @param level
         * @param nodeMap<level, node>
         */
        function getChildren(node, level, nodeMap) {
          node.children = node.children || []

          // if (level > 1) {
          if (level < TOP_LEVEL) {
            if (node.children.length === 0) {
              node.children[0] = nodeMap && nodeMap[TOP_LEVEL - level] || {_type: 'fake'}
            }
            // let lastChild = node.children[node.children.length - 1];
            let lastChild = node.children[0];
            // return getChildren(lastChild, level - 1)
            return getChildren(lastChild, level + 1)
            // } else if (level === 1) {
          } else if (level === TOP_LEVEL) {
            return node.children;
          }
        }

        function findTopNode(nodeId, nodes) {
          const node = nodes[nodeId];
          if (node.parentId) {
            return findTopNode(node.parentId, nodes)
          } else if (node.topId) {
            return nodes[node.topId]
          } else {
            return node
          }
        }

        //依据源节点的顶层节点id收集sharedLinks
        function collectSharedLinks(sourceId, link, nodes, sharedLinks) {
          const topNode = findTopNode(sourceId, nodes)
          const topNodeId = topNode.nodeId;
          sharedLinks[topNodeId] = sharedLinks[topNodeId] || []
          sharedLinks[topNodeId].push(link)
        }

        require(['data/hierarchical', 'data/csv', 'json!tmp/2018/0926/topo'], function(data, csv, json) {
          var rootNode = null;
          let nodeArr = _.map(json.data.nodes, node => {
            return {
              nodeId: node.id,
              type: node.type,
              name: node.name,
              level: node.level
            }
          })
          let linkArr = _.map(json.data.lines, (link, i) => {
            return {
              index: i,
              linkId: link.id,
              name: link.name,
              source: link.prevId,
              target: link.nextId
            }
          })
          let nodes = _.mapKeys(nodeArr, node => node.nodeId)
          let links = _.mapKeys(linkArr, link => link.linkId)

          let sameLevelLinks = []

          switch (3) {
            case 1:
              rootNode = d3.hierarchy(data)
              break;
            case 2:
              rootNode = d3.stratify()
                .id(function(d) { return d.name; })
                .parentId(function(d) { return d.parent; })
                (csv.data);
                break;
            case 3:
              /**
               * 改进层级布局，使支持层级定义和支持共源共目的连线，用了倒置式d3.hierarchy
               * @param nodes Object<id, {level...}>
               * @param links Object<id, {source: sourceNodeId, target: targetNodeId}>
               */
              let buildData = {_type: 'root', children:[]} //构造根节点
              let linkedIds = {};
              let sourceIds = {};
              // let targetIds = {}
              let sharedLinks = {}; //共用源的连线，只有一条可以直接挂载，其余将被收集在这里
              let normalLinks = {}
              //处理正常连接线
              _.each(links, (link, id) => {
                const sourceId = link.source;
                const targetId = link.target;
                if (!nodes[sourceId] || !nodes[targetId]) return
                //判断是否越级，越级则不能挂载
                if (nodes[targetId].level - nodes[sourceId].level === 1) {
                  // if (!targetIds[targetId]) {//有些节点有连线，但是其目的已成为其他节点的子节点，所以不能让其参与布局
                  if (!sourceIds[sourceId]) {
                    // nodes[sourceId].children = nodes[sourceId].children || []
                    // nodes[sourceId].children.push(nodes[targetId])
                    nodes[targetId].children = nodes[targetId].children || []
                    nodes[targetId].children.push(nodes[sourceId])
                    nodes[sourceId].parentId = targetId
                    // targetIds[targetId] = 1
                    sourceIds[sourceId] = 1
                    normalLinks[id] = 1
                    linkedIds[sourceId] = 1
                    linkedIds[targetId] = 1
                  }
                }
              })
              //处理非正常的越级线或源点已参与其他线的连线
              _.each(links, (link, id) => {
                const sourceId = link.source;
                const targetId = link.target;
                if (normalLinks[id]) return
                if (!nodes[sourceId] || !nodes[targetId]) return
                //判断是否越级，越级则不能挂载
                if (nodes[targetId].level - nodes[sourceId].level === 1) {
                  if (sourceIds[sourceId]) {//source被使用
                    //收集共用source的线
                    collectSharedLinks(sourceId, link, nodes, sharedLinks);
                  }
                } else if (nodes[targetId].level === nodes[sourceId].level) {
                  nodes[sourceId]._type = 'manual'
                  sameLevelLinks.push(link)
                  return
                } else {
                  //也收集跨级的线
                  collectSharedLinks(sourceId, link, nodes, sharedLinks);
                }

                linkedIds[sourceId] = 1
                linkedIds[targetId] = 1
              })

              let topNodeIds = {}
              _.each(sharedLinks, (linkList, sourceId) => { //此source必有一直接父级
                let sourceTopNode = findTopNode(sourceId, nodes)
                let sourceTopNodeId = sourceTopNode.nodeId

                let leftRightLinks = [];

                if (sourceTopNode.level !== TOP_LEVEL) {
                  //要创建补间节点
                  //源节点不是顶层节点时，用来补间的节点可以在links里找
                  let nodeMap = {}
                  let markLevelIndex = {} //记录level和索引之间的映射
                  _.each(linkList, (link, i) => {
                    const node = nodes[link.target]
                    if (!nodeMap[node.level]) {
                      nodeMap[node.level] = node;
                      markLevelIndex[node.level] = i
                    }
                  })

                  let realSourceTopNode = {_type: 'fake', nodeId: sourceTopNodeId}
                  if (nodeMap[TOP_LEVEL]) {
                    realSourceTopNode = nodeMap[TOP_LEVEL]
                    sourceTopNodeId = nodeMap[TOP_LEVEL].nodeId
                  }
                  
                  let sourceParentChildren = getChildren(realSourceTopNode, sourceTopNode.level + 1, nodeMap) || [];
                  sourceParentChildren.push(sourceTopNode);
                  sourceTopNode.topId = realSourceTopNode.nodeId;//直接通过topId索引到最顶层id，越过补间节点，findTopNode支持该参数
                  let originSourceTopNode = sourceTopNode;
                  sourceTopNode = realSourceTopNode;

                  //哪些node一定会被用于补间？ 从sourceTopNode.level 到TOP_LEVEL之间的
                  let markIndexLevel = {}
                  _.each(markLevelIndex, (index, level) => {
                    markIndexLevel[index] = level
                  })

                  _.each(linkList, (link, i) => {
                    if (markIndexLevel[i] > originSourceTopNode.level && markIndexLevel[i] <= TOP_LEVEL) {
                      //这里是一定被用用作补间的
                      nodeMap[markIndexLevel[i]].topId = realSourceTopNode.nodeId
                    } else {
                      leftRightLinks.push(link)
                    }
                  })
                } else {
                  leftRightLinks = linkList;
                }
                const len = leftRightLinks.length
                _.each(leftRightLinks, (link, i) => {
                  let targetTopNode = findTopNode(link.target, nodes)
                  const targetTopNodeId = targetTopNode.nodeId

                  if (targetTopNode.level !== TOP_LEVEL) {
                    //要创建补间节点
                    let realTargetTopNode = {_type: 'fake', nodeId: targetTopNodeId}
                    let targetParentChildren = getChildren(realTargetTopNode, targetTopNode.level + 1) || [];
                    targetParentChildren.push(targetTopNode);
                    targetTopNode = realTargetTopNode;
                  }

                  if (parseInt(len / 2) === i && !topNodeIds[sourceTopNodeId]) {
                    buildData.children.push(sourceTopNode)
                    topNodeIds[sourceTopNodeId] = 1
                  }

                  if (!topNodeIds[targetTopNodeId]) {
                    buildData.children.push(targetTopNode)
                    topNodeIds[targetTopNodeId] = 1
                  }
                })
              })

              //为孤立节点单独创建一棵树
              let treeWithoutLink = {_type: 'fake', children: [
                {_type: 'fake', children: [
                  {_type: 'fake', children: [
                    {_type: 'fake', children: []}
                  ]}
                ]}
              ]}

              _.each(nodes, (node, id) => {
                if (linkedIds[id]) { //有线并处理过结构的
                  let topNode = findTopNode(id, nodes)
                  const topNodeId = topNode.nodeId
                  if (!topNodeIds[topNodeId]) { //但是在上面没处理的
                    if (topNode.level !== TOP_LEVEL) {
                      let realTopNode = {_type: 'fake'}
                      let parentChildren = getChildren(realTopNode, topNode.level + 1) || []
                      parentChildren.push(topNode)
                      topNode = realTopNode
                    }
                    buildData.children.push(topNode)
                    topNodeIds[topNodeId] = 1
                  }
                } else if (node._type !== 'manual') { //没线的孤立节点
                  let children = getChildren(treeWithoutLink, node.level)
                  children.push(node)
                }
              })

              //将孤立节点树合并到主树
              _.each(treeWithoutLink.children, (child) => {
                buildData.children.push(child)
              })

              rootNode = d3.hierarchy(buildData);
              break;
          }
          
          var tree = d3.tree();

          // tree.size([400, 400])
          tree.nodeSize([100, 60]) //会和上面的冲突

          tree(rootNode)

          var svgDom = d3.select('#svg-3-1')

          var g = svgDom.append('g')

          var zoom = svgZoom(svgDom, g)

          rootNode.each(function(d) {
            if (d.data && d.data.nodeId) {
              nodes[d.data.nodeId].x = d.x;
              nodes[d.data.nodeId].y = d.y;
            }
          })

          _.each(sameLevelLinks, link => {
            if (nodes[link.source]._type === 'manual') {
              nodes[link.source].x = nodes[link.target].x + 100;
              nodes[link.source].y = nodes[link.target].y + 30;
            } else if (nodes[link.target]._type === 'manual') {
              nodes[link.target].x = nodes[link.source].x + 100;
              nodes[link.target].y = nodes[link.source].y + 30
            }
          })

          if (0) {
            var d3Links = rootNode.links()

            d3Links.forEach(function(link) {
              g.append('line')
                // .attr('x1', link.source.x)
                .attr('x1', link.target.x)
                // .attr('y1', 500 - link.source.y)
                .attr('y1', link.target.y)
                // .attr('x2', link.target.x)
                .attr('x2', link.source.x)
                // .attr('y2', 500 - link.target.y)
                .attr('y2', link.source.y)
                .attr('stroke', 'black')
                .attr('marker-end', 'url(#arrow2)')
            })
          } else {
            _.each(links, function(link) {
              if (link._type === 'fake') return
              g.append('line')
                .attr('x1', nodes[link.source].x)
                .attr('y1', 500 - nodes[link.source].y)
                .attr('x2', nodes[link.target].x)
                .attr('y2', 500 - nodes[link.target].y)
                .attr('stroke', 'red')
                .attr('marker-end', 'url(#arrow3)')
            })
          }

          rootNode.each(function(d) {
            // if (d.data._type) return
            g.append('circle')
              .attr('cx', d.x)
              .attr('cy', 500 - d.y)
              .attr('r', 10)
              .attr('fill', 'black')
              .attr('stroke', 'black')
            g.append('text')
              .attr('fill', 'white')
              .attr('x', d.x)
              .attr('y', 500 - d.y)
              .attr('text-anchor', 'middle')
              .attr('alignment-baseline', 'middle')
              .text(d.data.name ? 'A' : '')
              .attr('font-size', '10')
            g.append('text')
              .attr('fill', 'blue')
              .attr('x', d.x)
              .attr('y', 500 - d.y - 15)
              .attr('text-anchor', 'middle')
              // .attr('alignment-baseline', 'middle')
              .text(d.data.nodeId ? d.data.nodeId.substring(0, 16) : '')
              .attr('font-size', '10')
              .attr('font-weight', 'bold')
          })          
          zoom.translateBy(svgDom, 250, -200)
        })
      </script>
    </div>
  </div>
  
</div>